package mybatis.builder.xml;

import mybatis.exception.MyBatisException;
import mybatis.mapping.MappedStatement;
import mybatis.mapping.SqlCommandType;
import mybatis.session.Configuration;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import utils.LogUtils;

import java.io.InputStream;
import java.util.List;
import java.util.Objects;

/**
 * XML文件SQL语句映射关系解析构造器
 *
 * @author richie696
 * @version 1.0
 * @since 2020/11/08
 */
public class XmlMapperBuilder {

    // 主配置文件
    private final Configuration configuration;

    /**
     * SQL语句映射关系解析构造器构造函数
     *
     * @param configuration 主配置文件
     */
    public XmlMapperBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    /**
     * 解析器入口方法
     *
     * @param inputStream 配置文件输入流
     */
    public void parse(InputStream inputStream) {
        // 创建并校验XML解析器对象
        Document document = null;
        try {
            document = new SAXReader().read(inputStream);
        } catch (DocumentException e) {
            LogUtils.error(e.getMessage(), e);
        }
        if (Objects.isNull(document)) {
            String msg = "要解析的Mapper文件无效。";
            LogUtils.error(msg);
            throw new MyBatisException(msg);
        }
        // 获取根节点并验证
        List<Node> rootNodes = document.selectNodes("//mapper");
        if (rootNodes.size() != 1) {
            String msg = String.format("文件：%s，Mapper文件中只能有一个mapper标签。", document.getName());
            LogUtils.error(msg);
            throw new MyBatisException(msg);
        }
        // 验证通过后获取该文件的命名空间并再次验证
        Element rootElement = ((Element) rootNodes.get(0));
        String namespace = rootElement.attributeValue("namespace");
        if (StringUtils.isEmpty(namespace)) {
            String msg = String.format("文件：%s，Mapper文件中未定义namespace，" +
                    "重名查询ID可能会产生冲突，请指定该文件的namespace。", document.getName());
            LogUtils.error(msg);
            throw new MyBatisException(msg);
        }
        // 获取该节点下的所有查询节点标签
        List<Node> sqlNodes = rootElement.selectNodes("//*");
        // 遍历解析标签
        sqlNodes.forEach(node -> {
            Element element = (Element) node;
            parseSqlNode(namespace, element);
        });
    }

    /**
     * 解析SQL查询语句标签节点的方法
     *
     * @param namespace 命名空间
     * @param element   待解析的SQL语句节点
     */
    private void parseSqlNode(String namespace, Element element) {
        // 获取节点名称并取得SQL命令类型
        String name = element.getQName().getName();
        SqlCommandType sqlCommandType = SqlCommandType.nameOf(name);
        MappedStatement value;
        // 根据SQL命令类型执行相应的解析过程
        switch (sqlCommandType) {
            case SELECT:
                // 执行查询类解析
                value = parseSelectNode(element);
                break;
            case INSERT:
                // 执行插入类解析
                value = parseInsertNode(element);
                break;
            case DELETE:
                // 执行删除类解析
                value = parseDeleteNode(element);
                break;
            case UPDATE:
                // 执行更新类解析
                value = parseUpdateNode(element);
                break;
            default:
                // 执行其他节点的解析
                value = parseUnknownNode(element);
        }
        // 如果当前节点是SQL语句类型的节点，则执行语句对象的缓存操作
        if (sqlCommandType.isSqlCommandType()) {
            String key = String.format("%s.%s", namespace, element.attributeValue("id"));
            if (configuration.containMappedStatement(key)) {
                String msg = String.format("Mapper文件中定义的 namespace：“%s”重复，请确认。", key);
                LogUtils.error(msg);
                throw new MyBatisException(msg);
            }
            configuration.addMappedStatement(key, value.setSqlCommandType(sqlCommandType));
        }

    }

    /**
     * 执行查询类语句解析的方法
     *
     * @param element 待解析的节点对象
     * @return 返回解析后的SQL语句对象
     */
    private MappedStatement parseSelectNode(Element element) {
        MappedStatement mappedStatement = new MappedStatement();
        mappedStatement.setId(element.attributeValue("id"));
        String resultType = element.attributeValue("resultType");
        String parameterType = element.attributeValue("parameterType");
        Class<?> resultTypeClass = null;
        // 先校验是否是全限定类型，如果是则直接获取对应类型
        try {
            resultTypeClass = Class.forName(resultType);
        } catch (ClassNotFoundException ignored) {
        }
        // 如果不是全限定类型则获取Java基本类型关系
        if (Objects.isNull(resultTypeClass)) {
            resultTypeClass = configuration.getDefaultClass(resultType);
        }
        // 如果依旧不是基本类型则获取别名映射关系内定义的类型
        if (resultTypeClass == null) {
            resultTypeClass = configuration.getTypeAlias(resultType);
        }
        // 如果依旧找不到则当前是无效类型
        if (resultTypeClass == null) {
            throw new MyBatisException("当前ResultType使用的类型未定义，请添加到TypeAliases标签内。");
        }
        mappedStatement.setResultTypeClass(resultTypeClass);
        mappedStatement.setResultType(resultType);
        mappedStatement.setParameterType(parameterType);
        mappedStatement.setSql(element.getTextTrim());
        return mappedStatement;
    }

    /**
     * 执行插入类语句解析的方法
     *
     * @param element 待解析的节点对象
     * @return 返回解析后的SQL语句对象
     */
    private MappedStatement parseInsertNode(Element element) {
        MappedStatement mappedStatement = new MappedStatement();
        mappedStatement.setId(element.attributeValue("id"));
        String parameterType = element.attributeValue("parameterType");
        mappedStatement.setParameterType(parameterType);
        mappedStatement.setResultType("int");
        mappedStatement.setKeyProperty(element.attributeValue("keyProperty"));
        mappedStatement.setUseGeneratedKeys(Boolean.parseBoolean(element.attributeValue("useGeneratedKeys")));
        mappedStatement.setSql(element.getTextTrim());
        return mappedStatement;
    }

    /**
     * 执行更新类语句解析的方法
     *
     * @param element 待解析的节点对象
     * @return 返回解析后的SQL语句对象
     */
    private MappedStatement parseUpdateNode(Element element) {
        return generateUpdate(element);
    }

    /**
     * 执行删除类语句解析的方法
     *
     * @param element 待解析的节点对象
     * @return 返回解析后的SQL语句对象
     */
    private MappedStatement parseDeleteNode(Element element) {
        return generateUpdate(element);
    }

    private MappedStatement generateUpdate(Element element) {
        MappedStatement mappedStatement = new MappedStatement();
        mappedStatement.setId(element.attributeValue("id"));
        String parameterType = element.attributeValue("parameterType");
        mappedStatement.setParameterType(parameterType);
        mappedStatement.setResultType("int");
        mappedStatement.setSql(element.getTextTrim());
        return mappedStatement;
    }

    /**
     * 执行其它标签解析的方法
     *
     * @param element 待解析的节点对象
     * @return 返回解析后的对象
     */
    private MappedStatement parseUnknownNode(Element element) {
        // TODO: 待实现，解析cache, cache-ref, sql, resultMap, parameterMap节点
        return new MappedStatement();
    }

}
